//-----------------------------------------------------------------------------
//
// CodeXS - Copyright(c) Businessware Architects cc
// Contact: Willem J Fourie
// E-mail : willemf@bware.biz
//
// This file is part of CodeXS.
//
// CodeXS is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License (GPL) as 
// published by the Free Software Foundation version 2 of the License.
//
// CodeXS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with CodeXS; if not, write to:
//
// Free Software Foundation, Inc.
// 59 Temple Place
// Suite 330
// Boston
// MA  02111-1307  USA
//
//-----------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Collections;
using System.Xml.Serialization;
using System.CodeDom;
using Refly;
using Refly.CodeDom;
using Refly.CodeDom.Statements;
using Refly.CodeDom.Expressions;
using Refly.Templates;

namespace CodeXS.Schema.CodeBuilder
{
	public abstract class Base
	{
		public abstract CodeTypeDeclarationCollection ToCodeDom();
		protected NamespaceDeclaration _namespace = null;
		public NamespaceDeclaration Namespace
		{
			get { return _namespace; }
		}
		protected string _className = null;
		public string ClassName
		{
			get { return _className; }
		}
		protected string _classType = null;
		public string ClassType
		{
			get { return _classType; }
		}
		protected Base(string NamespaceName, string ClassName,
			string ClassType)
		{
			_namespace = new NamespaceDeclaration(NamespaceName);
			_className = LanguageBuilder.GetValidClassName(ClassName);
			_classType = ClassType;
		}
	}
	public class LanguageBuilder
	{
		static public string GetValidDeclarationName(string DeclName)
		{
			DeclName = LanguageBuilder.GetValidClassName(DeclName);
			CodeGenerator.Language lang = CodeGenerator.CodeLanguage;
			if(lang == CodeGenerator.Language.CS)
			{
				char[] specChars = new char[] { '@' };
				DeclName = DeclName.Trim(specChars);
			}
			if(lang == CodeGenerator.Language.VB)
			{
				char[] specChars = new char[] { '[', ']'};
				DeclName = DeclName.Trim(specChars);
			}
			return DeclName;
		}
		static public string GetValidParameterName(string DeclName)
		{
			// CHANGE: 2006-06-29:	  Version 0.57 Beta
			// Use method CollectionBuilder.GetCollectionNameFromArray.
			DeclName = RemoveStandardPrefixes(DeclName);
			return CollectionBuilder.
				GetCollectionNameFromArray(DeclName);
		}
		static public string GetValidCollectionName(string DeclName)
		{
			// CHANGE: 2006-06-29:	  Version 0.57 Beta
			// Use the property:
			// CollectionBuilder.CollectionSuffixString
			return DeclName + CollectionBuilder.CollectionSuffixString;
		}
		static public string RemoveStandardPrefixes(string Name)
		{
			string[] systemDot = { "System.Xml.", "System." };
			foreach(string s in systemDot)
			{
				if(Name.StartsWith(s)) Name = Name.Remove(0, s.Length);
			}
			return Name;
		}
		static public string GetValidClassName(string Name)
		{
			if(Name == null) return Name;
			// Fix if array type
			string arrayRank = CodeModifier.ArrayType.ArrayParentheses;
			if(Name.IndexOf(arrayRank) != -1)
			{
				Name = GetValidParameterName(Name);
			}
			else
			{
				Name = RemoveStandardPrefixes(Name);
			}
			return Name;
		}
		// CHANGE: 2006-07-04:	 Version 0.57 Beta
		// Made generic method to use reserved words as variables.
		static public string GetValidName(string standardName)
		{
			switch(standardName)
			{
				case "ref":
					if (CodeGenerator.CodeLanguage == CodeGenerator.Language.CS)
					{
						standardName = "@ref";
					}
					break;
			}
			switch(standardName.ToLower())
			{
				case "date":
					// CHANGE: 2006-06-29:	 Version 0.57 Beta
					// Should be VB.
					if (CodeGenerator.CodeLanguage == CodeGenerator.Language.VB)
					{
						standardName = "[date]";
					}
					break;
				case "string":
					// CHANGE: 2006-06-29:	 Version 0.57 Beta
					// string is reserved in C#, VB.
					switch (CodeGenerator.CodeLanguage)
					{
						case CodeGenerator.Language.CS:
							standardName = "@string";
							break;
						case CodeGenerator.Language.VB:
							standardName = "[string]";
							break;
					}
					break;
			}
			return standardName;
		}
	}
	public class CollectionGenerator : ITemplate
	{
		[Category("Collection")]
		public CodeTypeReference CollectionType = null;
		[Category("Data")]
		public String ElementType = null;
		[Category("Data")]
		public bool ItemGet = true;
		[Category("Data")]
		public bool ItemSet = true;
		[Category("Data")]
		public bool Add = true;
		[Category("Data")]
		public bool AddRange = true;
		[Category("Data")]
		public bool Contains = true;
		[Category("Data")]
		public bool Remove = true;
		[Category("Data")]
		public bool Insert = true;
		[Category("Data")]
		public bool IndexOf = true;
		[Category("Conversion")]
		public bool Convert = true;
		[Category("Enumerator")]
		public bool Enumerator = true;
		public CollectionGenerator()
		{}
		public ITypeDeclaration Type
		{
			get
			{
				if(this.ElementType == null)
				{
					throw new InvalidOperationException("Element type is undefined");
				}
				return new StringTypeDeclaration(this.ElementType);
			}
		}
		public ClassDeclaration AddClass(NamespaceDeclaration ns)
		{
			ClassDeclaration col = ns.AddClass(this.Name);
			// set base class as CollectionBase
			col.Parent = new TypeTypeDeclaration(typeof(CollectionBase));
			// default constructor
			// col.AddConstructor();
			// add indexer
			if(this.ItemGet || this.ItemSet)
			{
				IndexerDeclaration index = col.AddIndexer(this.Type);
				ParameterDeclaration pindex = index.Signature.AddParam(
					typeof(int), "index", false);
				// get body
				if(this.ItemGet)
				{
					index.Get.Return(
						(Expr.This.Prop("List").Item(Expr.Arg(pindex)).Cast(this.Type)));
				}
				// set body
				if (this.ItemSet)
				{
					index.Set.Add(
						Stm.Assign(
						Expr.This.Prop("List").Item(Expr.Arg(pindex)),
						Expr.Value));
				}
			}
			string pname = LanguageBuilder.GetValidDeclarationName(this.Type.Name);
			// CHANGE: 2006-06-29:	  Version 0.57 Beta
			// Use the method:
			// CollectionBuilder.CollectionSuffixString
			string colName = pname + CollectionBuilder.CollectionSuffixString;
			StringTypeDeclaration colNameDecl = new StringTypeDeclaration(colName);
			CollectionTypeDeclaration colDecl = new CollectionTypeDeclaration(this.Type.Name);
			string sname = colDecl.Name;
			string esname = CollectionBuilder.GetScalarArrayType(colName);
			string arname = CollectionBuilder.GetArrayTypeFromScalarArray(esname);
			int arrayRank = CollectionBuilder.GetArrayRank(esname);
			// TODO: Only works for single, double jagged arrays.
			string ecname = arname;
			string ecsname = arname;
			if (arrayRank == 2)
			{
				ecname += CollectionBuilder.CollectionSuffixString;
				ecsname += CodeModifier.ArrayType.ArrayParentheses;
			}
			if (sname.IndexOf(esname) > 0)
			{
				string prefix = sname.Replace(esname, string.Empty);
				esname = prefix + esname;
			}
			// conversion methods
			if(this.Convert)
			{
				// ToArray method
				// TODO: Create only works for single and double jagged arrays.
				string methodName = "ToArray";
				MethodDeclaration toArray = col.AddMethod(methodName);
				toArray.Signature.ReturnType = new StringTypeDeclaration(esname);
				toArray.Attributes = MemberAttributes.Public | MemberAttributes.Static;
				ParameterDeclaration para = toArray.Signature.AddParam(colNameDecl,
					colName, true);
				// CHANGE: 2006-06-19:	  Version 0.56 Beta
				// Add null check below:
				// if (<type>Collection == null) return null;
				ConditionStatement ifStm = Stm.IfNull(
					Expr.Snippet(colName), new Statement[] { Stm.Return(Expr.Null) });
				toArray.Body.Add(ifStm);
				// TODO: Only works for array rank 1 and 2.
				VariableDeclarationStatement vd0;
				VariableDeclarationStatement vd1;
				VariableDeclarationStatement vdi;
				IterationStatement ist;
				switch (arrayRank)
				{
					case 1:
						// return (<base-type>[])<<base-type>Collection>.
						//    InnerList.ToArray(typeof(<base-type>));
						toArray.Body.Return(Expr.Cast(toArray.Signature.ReturnType,
							Expr.Snippet(colName + ".InnerList").Method("ToArray").Invoke(
								new Expression[] { Expr.TypeOf(this.Type) })));
						break;
					case 2:
						// <base-type>Collection[] collection = 
						//    (<base-type>Collection[])<<base-type>Collection>.
						//        .InnerList.ToArray(typeof(<base-type>));
						vd0 = new VariableDeclarationStatement(
								new StringTypeDeclaration(sname), "collection");
						toArray.Body.Add(vd0);
						toArray.Body.AddAssign(
							Expr.Var(vd0),
							Expr.Cast(vd0.Type,
								Expr.Snippet(colName + ".InnerList").Method("ToArray").Invoke(
									new Expression[] { Expr.TypeOf(this.Type) })));
						// ArrayList array = new ArrayList();
						vd1 = new VariableDeclarationStatement(
								new StringTypeDeclaration("System.Collections.ArrayList"), "array");
						toArray.Body.Add(vd1);
						toArray.Body.AddAssign(
							Expr.Var(vd1),
							Expr.New(vd1.Type, new Expression[0]));
						// int i;
						vdi = new VariableDeclarationStatement(
								new TypeTypeDeclaration(typeof(int)), "i");
						toArray.Body.Add(vdi);
						// for (i = 0; i < <<base-type>Collection>.Count); i++)
						ist = Stm.For(
							Stm.Assign(Expr.Var(vdi), Expr.Snippet("0")),
							new BinaryOpOperatorExpression(Expr.Var(vdi),
							Expr.Snippet(para.Name).Prop("Count"), 
							CodeBinaryOperatorType.LessThan),
							Stm.Assign(Expr.Var(vdi),
							new BinaryOpOperatorExpression(Expr.Var(vdi),
							Expr.Snippet("1"), CodeBinaryOperatorType.Add)));
						toArray.Body.Add(ist);
						// array.Add(<<element-type>Collection>.ToArray(collection[i]));
						ist.Statements.Add(Expr.Var(vd1).Method("Add").Invoke(
							Expr.Snippet(ecname).Method("ToArray").Invoke(
							new Expression[] { Expr.Var(vd0).Item(Expr.Var(vdi)) })));
						// return (<base-type>[][])array.ToArray(typeof(<base-type>[]));
						toArray.Body.Return(Expr.Cast(toArray.Signature.ReturnType,
							Expr.Var(vd1).Method("ToArray").Invoke(
								new Expression[] { Expr.TypeOf(new StringTypeDeclaration(ecsname)) })));
						break;
					default:
						throw new InvalidOperationException(
							"Collection generation only supports " +
							"jagged arrays of up to 2 dimensions");
				}
				// FromArray method
				methodName = "FromArray";
				MethodDeclaration fromArray = col.AddMethod(methodName);
				fromArray.Signature.ReturnType = colNameDecl;
				fromArray.Attributes = MemberAttributes.Public | MemberAttributes.Static;
				para = fromArray.Signature.AddParam(new StringTypeDeclaration(esname),
					"Array", true);
				// <type>Collection collection;
				vd0 = new VariableDeclarationStatement(
					new StringTypeDeclaration(colName), "collection");
				fromArray.Body.Add(vd0);
				// collection = new <type>Collection();
				fromArray.Body.AddAssign(
					Expr.Var(vd0), 
					Expr.New(new StringTypeDeclaration(colName), new Expression[0]));
				// CHANGE: 2006-06-19:	  Version 0.56 Beta
				// Add null check below:
				// if (Array == null) return collection;
				ifStm = Stm.IfNull(
					Expr.Snippet(para.Name), new Statement[] { Stm.Return(Expr.Snippet(vd0.Name)) });
				fromArray.Body.Add(ifStm);
				// int i;
				vdi = new VariableDeclarationStatement(
						new TypeTypeDeclaration(typeof(int)), "i");
				fromArray.Body.Add(vdi);
				// for (i = 0; (i < Array.GetLength(0)); i = (i + 1))
				ist = Stm.For(
					Stm.Assign(Expr.Var(vdi), Expr.Snippet("0")),
					new BinaryOpOperatorExpression(Expr.Var(vdi),
					Expr.Snippet(para.Name).Method("GetLength").
						Invoke(new Expression[] { Expr.Snippet("0") } ), CodeBinaryOperatorType.LessThan),
					Stm.Assign(Expr.Var(vdi), 
					new BinaryOpOperatorExpression(Expr.Var(vdi),
					Expr.Snippet("1"), CodeBinaryOperatorType.Add)));
				fromArray.Body.Add(ist);
				switch (arrayRank)
				{
					case 1:
						// {
						//	 collection.Add(Array[i]);
						// }
						ist.Statements.Add(Expr.Var(vd0).Method("Add").Invoke(
							new Expression[] { Expr.Arg(para).Item(Expr.Var(vdi)) }));
						break;
					case 2:
						// collection.Add(<<base-type>Collection>.FromArray(Array[i]));
						ist.Statements.Add(Expr.Var(vd0).Method("Add").Invoke(
							Expr.Snippet(ecname).Method("FromArray").Invoke(
							new Expression[] { Expr.Arg(para).Item(Expr.Var(vdi)) })));
						break;
					default:
						throw new InvalidOperationException(
							"Collection generation only supports " +
							"jagged arrays of up to 2 dimensions");
				}
				// return collection;
				fromArray.Body.Return(Expr.Var(vd0));
			}
			// add method
			if(this.Add)
			{
				// CHANGE: 2006-06-19:	  Version 0.56 Beta
				// Changed return type to be 'int' - returns the
				// number of items in the list.
				// Thanks to Dave Waterworth <dwaterworth@westpac.com.au>
				// for this fix.
				MethodDeclaration add = col.AddMethod("Add");
				add.Signature.ReturnType = new TypeTypeDeclaration(typeof(int));
				ParameterDeclaration para = add.Signature.AddParam(this.Type,
					pname, true);
				add.Body.Return(
					Expr.This.Prop("List").Method("Add").Invoke(para)
					);
			}

			if (this.AddRange)
			{
				MethodDeclaration add = col.AddMethod("AddRange");
				ParameterDeclaration para = add.Signature.AddParam(colNameDecl, 
					colName, true);

				ForEachStatement fe = Stm.ForEach(
					this.Type,
					"item",
					Expr.Arg(para),
					false
					);
				fe.Body.Add(
					Expr.This.Prop("List").Method("Add").Invoke( fe.Local)
					);

				add.Body.Add(fe);
			}

			// contains method
			if (this.Contains)
			{
				MethodDeclaration contains = col.AddMethod("Contains");
				contains.Signature.ReturnType = new TypeTypeDeclaration(typeof(bool));
				ParameterDeclaration para = contains.Signature.AddParam(this.Type,pname,true);
				contains.Body.Return(
					Expr.This.Prop("List").Method("Contains").Invoke(para)
					);
			}

			// remove method
			if (this.Remove)
			{
				MethodDeclaration remove = col.AddMethod("Remove");
				ParameterDeclaration para = remove.Signature.AddParam(this.Type,pname,true);
				remove.Body.Add(
					Expr.This.Prop("List").Method("Remove").Invoke(para)
					);
			}

			// insert
			if (this.Insert)
			{
				MethodDeclaration insert = col.AddMethod("Insert");
				ParameterDeclaration index = insert.Signature.AddParam(typeof(int),"index",true);
				ParameterDeclaration para = insert.Signature.AddParam(this.Type,pname,true);
				insert.Body.Add(
					Expr.This.Prop("List").Method("Insert").Invoke(index,para)
					);
			}

			// indexof
			if (this.IndexOf)
			{
				MethodDeclaration indexof = col.AddMethod("IndexOf");
				ParameterDeclaration para = indexof.Signature.AddParam(this.Type,pname,true);
				indexof.Signature.ReturnType = new TypeTypeDeclaration(typeof(int));
				indexof.Body.Return(
					Expr.This.Prop("List").Method("IndexOf").Invoke(para)
					);
			}

			if (this.Enumerator)
			{
				// create subclass
				ClassDeclaration en = col.AddClass("Enumerator");
				// add wrapped field
				FieldDeclaration wrapped = en.AddField(
					typeof(IEnumerator),"enumerator"
					);
				// add IEnumerator
				if(CodeGenerator.CodeLanguage == CodeGenerator.Language.VB)
				{
					en.Parent = new TypeTypeDeclaration(typeof(object));
				}
				en.Interfaces.Add(typeof(IEnumerator));
				// add constructor
				ConstructorDeclaration cs = en.AddConstructor();
				ParameterDeclaration collection = cs.Signature.AddParam(colNameDecl, "Collection", true);
				cs.Body.Add(
					Stm.Assign(
					Expr.This.Field(wrapped),
					Expr.Arg(collection).Cast(typeof(CollectionBase)).Method("GetEnumerator").Invoke()
					)
					);

				// add current 
				PropertyDeclaration current = en.AddProperty(this.Type,"Current");
				current.Get.Return(
					(Expr.This.Field(wrapped).Prop("Current")).Cast(this.Type)
					);

				// add explicit interface implementation
				PropertyDeclaration currentEn = en.AddProperty(typeof(Object),"Current");
				currentEn.Get.Return(Expr.This.Prop(current));
				currentEn.PrivateImplementationType = wrapped.Type;

				// add reset
				MethodDeclaration reset = en.AddMethod("Reset");
				reset.ImplementationTypes.Add( wrapped.Type );
				reset.Body.Add( Expr.This.Field(wrapped).Method("Reset").Invoke());

				// add movenext
				MethodDeclaration movenext = en.AddMethod("MoveNext");
				movenext.ImplementationTypes.Add( wrapped.Type );
				movenext.Signature.ReturnType = new TypeTypeDeclaration(typeof(bool));
				movenext.Body.Return( Expr.This.Field(wrapped).Method("MoveNext").Invoke());

				// add get enumerator
				MethodDeclaration geten = col.AddMethod("GetEnumerator");
				geten.Attributes |= MemberAttributes.New;
				if(CodeGenerator.CodeLanguage == CodeGenerator.Language.CS)
				{
					geten.ImplementationTypes.Add( new TypeTypeDeclaration(typeof(IEnumerable)) );
				}
				geten.Signature.ReturnType = en;
				geten.Body.Return(Expr.New(en,Expr.This));
			}

			return col;
		}

		protected string Name
		{
			get
			{
				return LanguageBuilder.GetValidCollectionName(this.Type.Name);
			}
		}

		#region ITemplate Members
		public void Generate(NamespaceDeclaration ns)
		{
			AddClass(ns);
		}
		#endregion
	}

	public class CollectionTypeDeclaration : ITypeDeclaration
	{
		string _type;
		public CollectionTypeDeclaration(string BaseType)
		{
			this._type = BaseType + CodeModifier.ArrayType.ArrayParentheses;
		}

		#region ITypeDeclaration Members

		public String Name
		{
			get
			{
				return _type;
			}
		}
		public String FullName
		{
			get
			{
				return _type;
			}
		}
		public CodeTypeReference TypeReference
		{
			get
			{
				return new CodeTypeReference(_type);
			}
		}

		#endregion

	}

	public class CollectionBuilder : Base
	{
		// CHANGE: 2006-06-29:	  Version 0.57 Beta
		// Use this suffix:
		// CollectionBuilder.CollectionSuffixString
		// for creating collection classes.
		public static string CollectionSuffixString
		{
			get { return "Collection"; }
		}
		// CHANGE: 2006-06-29:	  Version 0.57 Beta
		// Public method to get element type name from
		// collection name.
		public static string GetElementTypeName(string collectionName)
		{
			if(collectionName.EndsWith(CollectionSuffixString))
			{
				collectionName = collectionName.Substring(0,
					collectionName.Length - CollectionSuffixString.Length);
			}
			return collectionName;
		}
		public static string GetScalarArrayType(string collectionName)
		{
			int i = 0;
			while(collectionName.EndsWith(CollectionSuffixString))
			{
				++i;
				collectionName = collectionName.Substring(0,
					collectionName.Length - CollectionSuffixString.Length);
			}
			while (i-- > 0)
			{
				collectionName += CodeModifier.ArrayType.ArrayParentheses;
			}
			return collectionName;
		}
		// CHANGE: 2006-06-29:	  Version 0.57 Beta
		// Public method to get element type name from
		// collection name, given the scalar type name for a single or
		// multi-dimensional array.
		public static string GetElementTypeName(string collectionName,
			string elementScalarTypeName)
		{
			if (collectionName.EndsWith(CollectionSuffixString) &&
				(collectionName != elementScalarTypeName) &&
				collectionName.StartsWith(elementScalarTypeName))
			{
				collectionName = collectionName.Substring(0,
					collectionName.Length - CollectionSuffixString.Length);
			}
			return collectionName;
		}
		// CHANGE: 2006-06-29:	  Version 0.57 Beta
		// Returns a collection name given an array specification.
		public static string GetCollectionNameFromArray(
			string scalarArrayString)
		{
			return scalarArrayString.Replace(
				CodeModifier.ArrayType.ArrayParentheses,
				CollectionSuffixString);
		}
		public static string GetArrayTypeFromScalarArray(
			string scalarArrayString)
		{
			return scalarArrayString.Replace(
				CodeModifier.ArrayType.ArrayParentheses,
				string.Empty);
		}
		public static int GetArrayRank(string scalarArrayType)
		{
			int i = 0;
			while(scalarArrayType.EndsWith(CodeModifier.ArrayType.ArrayParentheses))
			{
				++i;
				scalarArrayType = scalarArrayType.Substring(0, 
					scalarArrayType.Length - CodeModifier.ArrayType.ArrayParentheses.Length);
			}
			return i;
		}
		public static bool IsCollectionType(string collectionName,
			string elementScalarTypeName)
		{
			// CHANGE: 2006-07-08:	 Version 0.57 Beta
			// TODO: This may be a problem, if some of the 
			// 'System...' type names are used as types in this
			// namespace.
			elementScalarTypeName =
				LanguageBuilder.GetValidDeclarationName(elementScalarTypeName);
			return 
				(collectionName.EndsWith(CollectionSuffixString) &&
				(collectionName != elementScalarTypeName) &&
				collectionName.StartsWith(elementScalarTypeName));
		}
		// CHANGE: 2006-06-29:	  Version 0.57 Beta
		// Returns a collection name for an element type.
		public static string GetCollectionNameFromType(
			string elementTypeName)
		{
			int arrayRank = 0;
			while (elementTypeName.EndsWith(CodeModifier.ArrayType.ArrayParentheses))
			{
				++arrayRank;
				elementTypeName = elementTypeName.Substring(0, 
					elementTypeName.Length - 2);
			}
			for(int i = 0;  i < arrayRank; i++)
			{
				elementTypeName += CollectionSuffixString;
			}
			return elementTypeName;
		}
		// CHANGE: 2006-06-29:	 Version 0.57 Beta
		// Moved 'GetValidTypeName' to CollectionBuilder class.
		public static string GetValidTypeName(CodeTypeReference referenceType, 
			bool useScalarType)
		{
			string typeSuffix = string.Empty;
			string baseTypeName = referenceType.BaseType;
			bool isCollection = false;
			if(useScalarType)
			{
				baseTypeName = CodeModifier.ReferenceType.
					GetStringElementType(referenceType);
			}
			else
			{
				if(referenceType.ArrayRank >= 1)
				{
					isCollection = true;
					referenceType = referenceType.ArrayElementType;
					while(referenceType.ArrayRank >= 1)
					{
						// CHANGE: 2006-06-29:	  Version 0.57 Beta
						// Use CollectionBuilder.CollectionSuffixString.
						typeSuffix += CollectionBuilder.CollectionSuffixString;
						referenceType = referenceType.ArrayElementType;
					}
				}
			}
			if(isCollection)
			{
				baseTypeName = LanguageBuilder.RemoveStandardPrefixes(baseTypeName);
				baseTypeName += typeSuffix;
			}
			else
			{
				baseTypeName = 
					CodeBuilder.LanguageBuilder.GetValidName(baseTypeName);
			}
			return baseTypeName;		
		}
		private CodeTypeReference _collectionType;
		public CollectionBuilder(string NamespaceName, string ClassName,
			string ClassType, CodeTypeReference CollectionType)
			:
			base(NamespaceName, ClassName, ClassType)
		{
			_collectionType = CollectionType;
		}
		public override CodeTypeDeclarationCollection ToCodeDom()
		{
			CollectionGenerator generator = new CollectionGenerator();
			generator.CollectionType = _collectionType;
			//generator.ElementType = this.ClassType;
			generator.ElementType =
				CollectionBuilder.GetCollectionNameFromType(this.ClassType);
			ClassDeclaration classDecl = generator.AddClass(this.Namespace);
			CodeTypeDeclarationCollection types = 
				new CodeTypeDeclarationCollection();
			classDecl.ToCodeDom(types);
			return types;
		}
	}
	public class SerializationClassGenerator : ITemplate
	{
		[Category("Data")]
		public String ElementType = null;
		public ClassDeclaration AddClass(NamespaceDeclaration ns)
		{
			ns.Conformer.AddWord("Target");
			ns.Conformer.AddWord("Namespace");
			ns.Conformer.RemoveWord("Name");
			ns.Conformer.RemoveWord("Space");
			ClassDeclaration col = ns.AddClass(this.Name);
			// Add the TargetNamespace property
			string[] nsDesc = this.GetStrings(_schemaNamespaces[0]);
			PropertyDeclaration prop = col.AddProperty(typeof(string), "TargetNamespace");
			prop.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			prop.Get.Return(Expr.Prim(nsDesc[1]));
			// Add the GetNamespaces method
			// static public XmlSerializerNamespaces GetNamespaces();
			MethodDeclaration getNs = col.AddMethod("GetNamespaces");
			getNs.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			ITypeDeclaration itype = new StringTypeDeclaration("XmlSerializerNamespaces") 
				as ITypeDeclaration;
			getNs.Signature.ReturnType = itype;
			// XmlSerializerNamespaces ns;
			// ns = new XmlSerializerNamespaces();
			VariableDeclarationStatement vd0 = 
				new VariableDeclarationStatement(itype, "ns");
			getNs.Body.Add(vd0);
			getNs.Body.AddAssign(
				Expr.Var(vd0),
				Expr.New(itype, new Expression[0]));
			// Repeat for each namespace
			// ns.Add("prefix", "namespace");
			foreach(string desc in _schemaNamespaces)
			{
				nsDesc = this.GetStrings(desc);
				getNs.Body.Add(
					Expr.Var(vd0).Method("Add").Invoke(new Expression[]
						{ Expr.Prim(nsDesc[0]), Expr.Prim(nsDesc[1]) } ));
			}
			// return ns;
			getNs.Body.Return(Expr.Var(vd0));
			// Add the ToXml method
			//static public string ToXml(object Obj, System.Type ObjType)
			MethodDeclaration toXml = col.AddMethod("ToXml");
			toXml.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			toXml.Signature.AddParam(typeof(object), "Obj", true);
			toXml.Signature.AddParam(typeof(System.Type), "ObjType", true);
			toXml.Signature.ReturnType =
				new StringTypeDeclaration("System.String") as ITypeDeclaration;
			// XmlSerializer ser = new XmlSerializer(ObjType, Serializer.TargetNamespace);
			itype = new StringTypeDeclaration("XmlSerializer") 
				as ITypeDeclaration;
			vd0 = new VariableDeclarationStatement(itype, "ser");
			toXml.Body.Add(vd0);
			toXml.Body.AddAssign(
				Expr.Var(vd0), 
				Expr.New(itype, new Expression[]
					{ Expr.Arg(toXml.Signature.Parameters[1]), 
					  Expr.Snippet("FrXSDLib.Serializer").Prop("TargetNamespace"),
					} ));
			// MemoryStream memStream = new MemoryStream();
			itype = new StringTypeDeclaration("MemoryStream") 
				as ITypeDeclaration;
			VariableDeclarationStatement vd1 = 
				new VariableDeclarationStatement(itype, "memStream");
			toXml.Body.Add(vd1);
			toXml.Body.AddAssign(
				Expr.Var(vd1), 
				Expr.New(itype, new Expression[0]));
			// XmlTextWriter xmlWriter = new XmlTextWriter(memStream, Encoding.UTF8);
			itype = new StringTypeDeclaration("XmlTextWriter") 
				as ITypeDeclaration;
			VariableDeclarationStatement vd2 = 
				new VariableDeclarationStatement(itype, "xmlWriter");
			toXml.Body.Add(vd2);
			toXml.Body.AddAssign(
				Expr.Var(vd2), 
				Expr.New(itype, new Expression[]
					{ Expr.Var(vd1), 
					  Expr.Snippet("Encoding.UTF8"), } ));
			// if(Serializer.PrettyPrint == true)
			ConditionStatement ifStm = new ConditionStatement(
                Expr.Snippet("FrXSDLib.Serializer").Prop("PrettyPrint"),
					new Statement[0]);
			toXml.Body.Add(ifStm);
			// {
			//   xmlWriter.Formatting = Formatting.Indented;
			//   xmlWriter.Indentation = 1;
			//   xmlWriter.IndentChar = Convert.ToChar((int)'\t');
			// }
			AssignStatement asStm = 
				new AssignStatement(Expr.Var(vd2).Prop("Formatting"), 
					Expr.Snippet("Formatting.Indented"));
			ifStm.TrueStatements.Add(asStm);
			asStm = new AssignStatement(Expr.Var(vd2).Prop("Indentation"),
				Expr.Prim((int)1));
			ifStm.TrueStatements.Add(asStm);
			asStm = new AssignStatement(Expr.Var(vd2).Prop("IndentChar"),
				Expr.Snippet("Convert").Method("ToChar").Invoke(
					new Expression[]{ Expr.Prim('\t') }	));
			ifStm.TrueStatements.Add(asStm);
			// xmlWriter.Namespaces = true;
			toXml.Body.AddAssign(Expr.Var(vd2).Prop("Namespaces"), Expr.Prim(true));
			// serializer.Serialize(xmlWriter, Obj, Serializer.GetNamespaces());
			toXml.Body.Add(Expr.Var(vd0).Method("Serialize").Invoke(
				new Expression[]
					{ Expr.Var(vd2), 
					  Expr.Arg(toXml.Signature.Parameters[0]),
					  Expr.Snippet("FrXSDLib.Serializer").Method("GetNamespaces").
						Invoke(new Expression[0])
					} ));
			// xmlWriter.Close();
			toXml.Body.Add(Expr.Var(vd2).Method("Close").Invoke());
			// memStream.Close();
			toXml.Body.Add(Expr.Var(vd1).Method("Close").Invoke());
			// string xml = Encoding.UTF8.GetString(memStream.GetBuffer());
			itype = new StringTypeDeclaration("System.String") 
				as ITypeDeclaration;
			VariableDeclarationStatement vd3 = 
				new VariableDeclarationStatement(itype, "xml");
			toXml.Body.Add(vd3);
			toXml.Body.AddAssign(
				Expr.Var(vd3),
				Expr.Snippet("Encoding.UTF8").Method("GetString").
					Invoke(new Expression[]
						{ Expr.Var(vd1).Method("GetBuffer").Invoke() } ));
			// xml = xml.Substring(xml.IndexOf(Convert.ToChar('<')));
			toXml.Body.AddAssign(
				Expr.Var(vd3),
				Expr.Var(vd3).Method("Substring").
					Invoke(new Expression[]
					{
						Expr.Var(vd3).Method("IndexOf").
						Invoke(new Expression[]
							{ 
								Expr.Snippet("Convert").Method("ToChar").Invoke(
								new Expression[]{ Expr.Prim('<') } ) 
							} )
					} ));
				// xml = xml.Substring(0, xml.LastIndexOf(Convert.ToChar('>')) + 1);
				toXml.Body.AddAssign(
					Expr.Var(vd3),
					Expr.Var(vd3).Method("Substring").
					Invoke(new Expression[]
					{
						Expr.Prim((int)0),
						Expr.Var(vd3).Method("LastIndexOf").
						Invoke(new Expression[]
						{
							Expr.Snippet("Convert").Method("ToChar").Invoke(
							new Expression[]
							{
								Expr.Prim('>')
							} )
						} ) +
						Expr.Prim((int)1)
					} ));
			// return xml;
			toXml.Body.Return(Expr.Var(vd3));
			// Add the FromXml method
			// static public object FromXml(string ObjXml, System.Type ObjType)
			MethodDeclaration fromXml = col.AddMethod("FromXml");
			fromXml.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			fromXml.Signature.AddParam(typeof(string), "Xml", true);
			fromXml.Signature.AddParam(typeof(System.Type), "ObjType", true);
			fromXml.Signature.ReturnType =
				new StringTypeDeclaration("System.Object") as ITypeDeclaration;
			// XmlSerializer ser = new XmlSerializer(ObjType);
			itype = new StringTypeDeclaration("XmlSerializer")
				as ITypeDeclaration;
			vd0 = new VariableDeclarationStatement(itype, "ser");
			fromXml.Body.Add(vd0);
			fromXml.Body.AddAssign(
				Expr.Var(vd0), 
				Expr.New(itype, new Expression[]
					{ Expr.Arg(fromXml.Signature.Parameters[1]), } ));
			// StringReader stringReader = new StringReader(ObjXml);
			itype = new StringTypeDeclaration("StringReader") 
				as ITypeDeclaration;
			vd1 = new VariableDeclarationStatement(itype, "stringReader");
			fromXml.Body.Add(vd1);
			fromXml.Body.AddAssign(
				Expr.Var(vd1), 
				Expr.New(itype, new Expression[]
					{ Expr.Arg(fromXml.Signature.Parameters[0]), } ));
			// XmlTextReader xmlReader = new XmlTextReader(stringReader);
			itype = new StringTypeDeclaration("XmlTextReader") 
				as ITypeDeclaration;
			vd2 = new VariableDeclarationStatement(itype, "xmlReader");
			fromXml.Body.Add(vd2);
			fromXml.Body.AddAssign(
				Expr.Var(vd2),
				Expr.New(itype, new Expression[]
					{ Expr.Var(vd1), } ));
			// object obj = serializer.Deserialize(xmlReader);
			itype = new StringTypeDeclaration("System.Object") as ITypeDeclaration;
			vd3 = new VariableDeclarationStatement(itype, "obj");
			fromXml.Body.Add(vd3);
			fromXml.Body.AddAssign(
				Expr.Var(vd3),
				Expr.Var(vd0).Method("Deserialize").
					Invoke(new Expression[]
						{ Expr.Var(vd2), }));
			// xmlReader.Close();
			fromXml.Body.Add(Expr.Var(vd2).Method("Close").Invoke());
			// stringReader.Close();
			fromXml.Body.Add(Expr.Var(vd1).Method("Close").Invoke());
			// return obj;
			fromXml.Body.Return(Expr.Var(vd3));
			return col;
		}		
		protected string Name
		{
			get { return "Serializer"; }
		}
		private string[] _schemaNamespaces = null;
		public SerializationClassGenerator(string[] SchemaNamespaces)
		{
			_schemaNamespaces = SchemaNamespaces;
		}
		private string[] GetStrings(string Descriptor)
		{
			if(Descriptor.StartsWith("\"\""))
			{
				Descriptor = "\" " + Descriptor.Substring(1);
			}
			Descriptor = Descriptor.Trim(new char[]{'\"'});
			Descriptor = Descriptor.Replace("\",\"", "\0");
			string[] strings = Descriptor.Split(new char[]{'\0'});
			if(strings[0] == " ") strings[0] = "";
			return strings;
		}
		#region ITemplate Members
		public void Generate(NamespaceDeclaration ns)
		{
			AddClass(ns);
		}
		#endregion
	}
	public class SerializationClassBuilder : Base
	{
		private string[] _schemaNamespaces = null;
		public SerializationClassBuilder(string NamespaceName, 
			string ClassName, string ClassType, string[] SchemaNamespaces)
			:
			base(NamespaceName, ClassName, ClassType)
		{
			_schemaNamespaces = SchemaNamespaces;
		}
		public override CodeTypeDeclarationCollection ToCodeDom()
		{
			SerializationClassGenerator generator = 
				new SerializationClassGenerator(_schemaNamespaces);
			generator.ElementType = this.ClassType;
			ClassDeclaration classDecl = generator.AddClass(this.Namespace);
			CodeTypeDeclarationCollection types = 
				new CodeTypeDeclarationCollection();
			classDecl.ToCodeDom(types);

			return types;
		}
	}
	public class SerializationSupportGenerator : ITemplate
	{
		[Category("Data")]
		public String ElementType = null;
		public ITypeDeclaration Type
		{
			get
			{
				if (this.ElementType==null)
					throw new InvalidOperationException("Element type is undefined");
				return new StringTypeDeclaration(this.ElementType);
			}
		}
		public ClassDeclaration AddClass(NamespaceDeclaration ns)
		{
			ClassDeclaration col = ns.AddClass(this.Name);
			MethodDeclaration fromXml = col.AddMethod("FromXml");
			fromXml.Attributes = MemberAttributes.Public | MemberAttributes.Static;
			ParameterDeclaration parm =
				fromXml.Signature.AddParam(typeof(string), "Xml", true);
			fromXml.Signature.ReturnType =
				new StringTypeDeclaration(this.Type.Name) as ITypeDeclaration;
			// return ({type})Serializer.FromXml(Xml, typeof({type}));
			fromXml.Body.Return(Expr.Cast(fromXml.Signature.ReturnType,
				Expr.Snippet("FrXSDLib.Serializer").Method("FromXml").Invoke(
					new Expression[]
					{ Expr.Arg(parm), 
						Expr.TypeOf(fromXml.Signature.ReturnType), } )));
			PropertyDeclaration toXml = col.AddProperty(typeof(string), "Xml");
			toXml.Attributes = MemberAttributes.Public;
			// return Serializer.ToXml(this, this.GetType());
            toXml.Get.Return(Expr.Snippet("FrXSDLib.Serializer").Method("ToXml").Invoke(
				new Expression[]
				{ Expr.This, Expr.This.Method("GetType").Invoke() } ));
			return col;
		}		
		protected string Name
		{
			get { return this.Type.Name; }
		}
		#region ITemplate Members
		public void Generate(NamespaceDeclaration ns)
		{
			AddClass(ns);
		}
		#endregion
	}
	public class SerializationSupportBuilder : Base
	{
		public SerializationSupportBuilder(string NamespaceName,
			string ClassName, string ClassType)
			:
			base(NamespaceName, ClassName, ClassType)
		{
		}
		public override CodeTypeDeclarationCollection ToCodeDom()
		{
			SerializationSupportGenerator generator = 
				new SerializationSupportGenerator();
			generator.ElementType = this.ClassType;
			ClassDeclaration classDecl = generator.AddClass(this.Namespace);
			CodeTypeDeclarationCollection types = 
				new CodeTypeDeclarationCollection();
			classDecl.ToCodeDom(types);
			return types;
		}
	}
}
